1 using System;
2 using System.Collections.Generic;
3 using UnityEngine;
4
5 namespace ProceduralToolkit
6 {
7 /// <summary>
8 /// Representation of color in HSV model
9 /// </summary>
10 public struct ColorHSV
11 {
12 /// <summary>
13 /// Hue component of the color
14 /// </summary>
15 public float h;
16
17 /// <summary>
18 /// Saturation component of the color
19 /// </summary>
20 public float s;
21
22 /// <summary>
23 /// Value component of the color
24 /// </summary>
25 public float v;
26
27 /// <summary>
28 /// Alpha component of the color
29 /// </summary>
30 public float a;
31
32 /// <summary>
33 /// Returns opposite color on the color wheel
34 /// </summary>
35 public ColorHSV complementary { get { return WithOffsetH(180); } }
36
37 public float this[int index]
38 {
39 get
40 {
41 switch (index)
42 {
43 case 0:
44 return h;
45 case 1:
46 return s;
47 case 2:
48 return v;
49 case 3:
50 return a;
51 default:
52 throw new IndexOutOfRangeException("Invalid ColorHSV index!");
53 }
54 }
55 set
56 {
57 switch (index)
58 {
59 case 0:
60 h = value;
61 break;
62 case 1:
63 s = value;
64 break;
65 case 2:
66 v = value;
67 break;
68 case 3:
69 a = value;
70 break;
71 default:
72 throw new IndexOutOfRangeException("Invalid ColorHSV index!");
73 }
74 }
75 }
76
77 /// <summary>
78 /// Constructs a new ColorHSV with given h, s, v, a components
79 /// </summary>
80 /// <param name="h">Hue component</param>
81 /// <param name="s">Saturation component</param>
82 /// <param name="v">Value component</param>
83 /// <param name="a">Alpha component</param>
84 public ColorHSV(float h, float s, float v, float a)
85 {
86 this.h = h;
87 this.s = s;
88 this.v = v;
89 this.a = a;
90 }
91
92 /// <summary>
93 /// Constructs a new ColorHSV with given h, s, v components and sets alpha to 1
94 /// </summary>
95 /// <param name="h">Hue component</param>
96 /// <param name="s">Saturation component</param>
97 /// <param name="v">Value component</param>
98 public ColorHSV(float h, float s, float v)
99 {
100 this.h = h;
101 this.s = s;
102 this.v = v;
103 a = 1;
104 }
105
106 /// <summary>
107 /// Constructs a new ColorHSV from a Color
108 /// </summary>
109 public ColorHSV(Color color)
110 {
111 Color.RGBToHSV(color, out h, out s, out v);
112 a = color.a;
113 }
114
115 public static explicit operator Vector4(ColorHSV c)
116 {
117 return new Vector4(c.h, c.s, c.v, c.a);
118 }
119
120 public static bool operator ==(ColorHSV lhs, ColorHSV rhs)
121 {
122 return (Vector4) lhs == (Vector4) rhs;
123 }
124
125 public static bool operator !=(ColorHSV lhs, ColorHSV rhs)
126 {
127 return !(lhs == rhs);
128 }
129
130 /// <summary>
131 /// Returns a nicely formatted string for this color
132 /// </summary>
133 public override string ToString()
134 {
135 return string.Format("HSVA({0:F3}, {1:F3}, {2:F3}, {3:F3})", h, s, v, a);
136 }
137
138 /// <summary>
139 /// Returns the color as a hexadecimal string in the format "RRGGBB"
140 /// </summary>
141 public string ToHtmlStringRGB()
142 {
143 return ColorUtility.ToHtmlStringRGB(ToColor());
144 }
145
146 /// <summary>
147 /// Returns the color as a hexadecimal string in the format "RRGGBBAA"
148 /// </summary>
149 public string ToHtmlStringRGBA()
150 {
151 return ColorUtility.ToHtmlStringRGBA(ToColor());
152 }
153
154 public override int GetHashCode()
155 {
156 return ((Vector4) this).GetHashCode();
157 }
158
159 public override bool Equals(object other)
160 {
161 if (!(other is ColorHSV))
162 {
163 return false;
164 }
165 ColorHSV color = (ColorHSV) other;
166 if (h.Equals(color.h) && s.Equals(color.s) && v.Equals(color.v))
167 {
168 return a.Equals(color.a);
169 }
170 return false;
171 }
172
173 /// <summary>
174 /// Converts ColorHSV to a RGB representation
175 /// </summary>
176 public Color ToColor()
177 {
178 var color = Color.HSVToRGB(h, s, v);
179 color.a = a;
180 return color;
181 }
182
183 /// <summary>
184 /// Returns new color with hue offset by <paramref name="angle"/> degrees
185 /// </summary>
186 public ColorHSV WithOffsetH(float angle)
187 {
188 return WithH(Mathf.Repeat(h + angle/360, 1));
189 }
190
191 /// <summary>
192 /// Returns new color with modified hue component
193 /// </summary>
194 public ColorHSV WithH(float h)
195 {
196 return new ColorHSV(h, s, v, a);
197 }
198
199 /// <summary>
200 /// Returns new color with modified saturation component
201 /// </summary>
202 public ColorHSV WithS(float s)
203 {
204 return new ColorHSV(h, s, v, a);
205 }
206
207 /// <summary>
208 /// Returns new color with modified value component
209 /// </summary>
210 public ColorHSV WithV(float v)
211 {
212 return new ColorHSV(h, s, v, a);
213 }
214
215 /// <summary>
216 /// Returns new color with modified saturation and value components
217 /// </summary>
218 public ColorHSV WithSV(float s, float v)
219 {
220 return new ColorHSV(h, s, v, a);
221 }
222
223 /// <summary>
224 /// Returns new color with modified alpha component
225 /// </summary>
226 public ColorHSV WithA(float a)
227 {
228 return new ColorHSV(h, s, v, a);
229 }
230
231 /// <summary>
232 /// Returns list of this color, <paramref name="count"/> of analogous colors and optionally complementary color
233 /// </summary>
234 public List<ColorHSV> GetAnalogousPalette(int count = 2, bool withComplementary = false)
235 {
236 const float analogousAngle = 30;
237
238 var palette = new List<ColorHSV> {this};
239 int rightCount = count/2;
240 int leftCount = count - rightCount;
241
242 for (int i = 0; i < leftCount; i++)
243 {
244 palette.Add(WithOffsetH(-(i + 1)*analogousAngle));
245 }
246 for (int i = 0; i < rightCount; i++)
247 {
248 palette.Add(WithOffsetH((i + 1)*analogousAngle));
249 }
250 if (withComplementary)
251 {
252 palette.Add(complementary);
253 }
254 return palette;
255 }
256
257 /// <summary>
258 /// Returns list of this color, two triadic colors and optionally complementary color
259 /// </summary>
260 public List<ColorHSV> GetTriadicPalette(bool withComplementary = false)
261 {
262 const float triadicAngle = 120;
263
264 var palette = new List<ColorHSV>
265 {
266 this,
267 WithOffsetH(-triadicAngle),
268 WithOffsetH(triadicAngle)
269 };
270 if (withComplementary)
271 {
272 palette.Add(complementary);
273 }
274 return palette;
275 }
276
277 /// <summary>
278 /// Returns list of this color and three tetradic colors
279 /// </summary>
280 public List<ColorHSV> GetTetradicPalette()
281 {
282 const float tetradicAngle = 60;
283
284 var palette = new List<ColorHSV>
285 {
286 this,
287 WithOffsetH(tetradicAngle),
288 complementary,
289 complementary.WithOffsetH(tetradicAngle)
290 };
291 return palette;
292 }
293
294 /// <summary>
295 /// Linearly interpolates between colors a and b by t.
296 /// </summary>
297 public static ColorHSV Lerp(ColorHSV a, ColorHSV b, float t)
298 {
299 t = Mathf.Clamp01(t);
300 return LerpUnclamped(a, b, t);
301 }
302
303 /// <summary>
304 /// Linearly interpolates between colors a and b by t.
305 /// </summary>
306 public static ColorHSV LerpUnclamped(ColorHSV a, ColorHSV b, float t)
307 {
308 float deltaH = Mathf.Repeat(b.h - a.h, 1);
309 if (deltaH > 0.5f)
310 {
311 deltaH -= 1;
312 }
313 return new ColorHSV(
314 Mathf.Repeat(a.h + deltaH*t, 1),
315 a.s + (b.s - a.s)*t,
316 a.v + (b.v - a.v)*t,
317 a.a + (b.a - a.a)*t);
318 }
319 }
320 }